/**
 * 
 */
package xiaojian.toolkit.fsm.tmpl;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import xiaojian.toolkit.fsm.FSMContext;
import xiaojian.toolkit.fsm.FiniteStateMachine;
import xiaojian.toolkit.fsm.State;
import xiaojian.toolkit.fsm.StatesFactory;
import xiaojian.toolkit.fsm.tmpl.annotation.OnAccept;
import xiaojian.toolkit.fsm.tmpl.annotation.OnEnter;
import xiaojian.toolkit.fsm.tmpl.annotation.OnLeave;
import xiaojian.toolkit.fsm.tmpl.annotation.OnTimeout;
import xiaojian.toolkit.fsm.tmpl.annotation.StateTemplate;
import xiaojian.toolkit.fsm.tmpl.state.StateAdapter;


/**
 * @author isdom
 *
 */
public class TemplateStatesFactory implements StatesFactory {

    private static final Logger logger = 
    	LoggerFactory.getLogger(TemplateStatesFactory.class);
    
	private	List<Object>	templates = new ArrayList<Object>();
	
	/**
	 * @param template the template to set
	 */
	public void setTemplate(Object template) {
		this.templates.add( template );
	}

	public void setTemplateCollection(Collection<Object> templates) {
		this.templates.clear();
		this.templates.addAll( templates );
	}
	
	static public Method[] getAllMethodsOfClass(Class<?> cls) {
    	Method[] methods = new Method[0];
        
        Class<?> itr = cls;
        while ( (null != itr) && !itr.equals(Object.class)) {
            methods = ArrayUtils.addAll(itr.getDeclaredMethods(), methods);
            itr = itr.getSuperclass();
        }
        
        return	methods;
    }

	private boolean checkParams(Class<?>[] params) {
		return ( 2 == params.length 
				&& FiniteStateMachine.class.equals(params[0])
				&& FSMContext.class.isAssignableFrom( params[1] ));
	}
	
	private void processMethod(StateAdapter state, Method m) {
		OnEnter spe = m.getAnnotation(OnEnter.class);
		OnLeave spl = m.getAnnotation(OnLeave.class);
		OnAccept spa = m.getAnnotation(OnAccept.class);
		OnTimeout spt = m.getAnnotation(OnTimeout.class);
		
		Class<?>[] params = m.getParameterTypes();
		
		if ( null != spe ) {
			if ( checkParams(params) ) {
				state.setMethodOfEnter(m);
			}
			else {
				logger.error("method {} mismatch params, can't used as OnEnter method", m);
			}
		}
		else if ( null != spl ) {
			if ( checkParams(params) ) {
				state.setMethodOfLeave(m);
			}
			else {
				logger.error("method {} mismatch params, can't used as OnLeave method", m);
			}
		}
		else if ( null != spa ) {
			if ( 3 == params.length 
					&& FiniteStateMachine.class.equals(params[0])
					&& FSMContext.class.isAssignableFrom(params[1])) {
				state.addMethodOfAccept(params[2], m);
			}
			else {
				logger.warn("method {} mismatch params, can't used as OnAccept method", m);
			}
		}
		else if ( null != spt ) {
			if ( checkParams(params) ) {
				state.setMethodOfTimeout(m);
			}
			else {
				logger.error("method {} mismatch params, can't used as OnTimeout method", m);
			}
		}
	}
	
	private boolean isStateTemplate(Class<?> c) {
		return	null != c.getAnnotation(StateTemplate.class);
	}
	
	private boolean isInitState(Class<?> c) {
		return	(null != c.getAnnotation(StateTemplate.class) )
					&& c.getAnnotation(StateTemplate.class).init();
	}
	
	private Object createInnerInstance(Class<?> inner, Object outer) throws Exception {
		Constructor<?> ctor = inner.getDeclaredConstructors()[0];
		ctor.setAccessible(true);
		return ctor.newInstance(outer);
	}
	
	private State processTemplate(Object template) throws Exception {
		StateAdapter state = new StateAdapter();
		
		state.setTarget(template);
		Class<?> c = template.getClass();
		
		Method[] methods = getAllMethodsOfClass(c);
		
		for (Method m : methods) {
			if ( logger.isDebugEnabled() ) {
				logger.debug("process method " + m);
			}
			processMethod(state, m);
		}
		state.setNameOfClass(c);
		
		return	state;
	}
	
	/* (non-Javadoc)
	 * @see com.skymobi.fsm.StatesFactory#createStates()
	 */
	public State[] createStates() {
		
		List<Object>	sources = new ArrayList<Object>();
		
		for ( Object outer : this.templates ) {
			if ( isStateTemplate(outer.getClass()) ) {
				sources.add(outer);
			}
			
			for ( Class<?> eachCls = outer.getClass();
				!eachCls.equals(Object.class); 
				eachCls = eachCls.getSuperclass()) {
				for ( Class<?> c : eachCls.getDeclaredClasses() ) {
					if ( isStateTemplate(c) ) {
						try {
							sources.add( createInnerInstance(c, outer) );
						} catch (Exception e) {
							logger.error("createInnerInstance:", e);
						}
					}
				}
			}
		}
		
		boolean	isInitStateDefined = false;
		
		List<State>	ret = new ArrayList<State>();
		
		for ( Object template : sources ) {
			if ( logger.isDebugEnabled() ) {
				logger.debug(template.getClass() + " is state template. try generate state");
			}
			
			State state = null;
			try {
				state = processTemplate(template);
			}
			catch (Exception e) {
				logger.error("processTemplate:", e);
			}

			if ( null != state ) {
				if ( isInitState(template.getClass()) ) {
					if ( isInitStateDefined ) {
						throw new RuntimeException("more than one init state template.");
					}
					isInitStateDefined = true;
					ret.add(0, state);
				}
				else {
					ret.add(state);
				}
			}
		}
		
		if ( !isInitStateDefined ) {
			logger.warn("Non Init State defined, just use first State as init state.");
		}
		
		return ret.toArray(new State[ret.size()]);
	}

}
